package com.msb.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.msb.common.utils.PageUtils;
import com.msb.common.utils.Query;
import com.msb.mall.product.service.CategoryBrandRelationService;
import com.msb.mall.product.service.CategoryService;
import com.msb.mall.product.dao.CategoryDao;
import com.msb.mall.product.entity.CategoryEntity;
import com.msb.mall.product.vo.Catalog2VO;
import org.apache.skywalking.apm.toolkit.trace.Tag;
import org.apache.skywalking.apm.toolkit.trace.Tags;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private String redis_key = "CatalogJSON";

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );
        return new PageUtils(page);
    }

    /*
     *查询素有的类别数据 然后将数据封装成为树形结构
     * @param params
     * @return
     */
    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        // 1. 查询所有的商品分类信息
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        // 2. 将所有的商品信息拆解成为树形结构(父子关系)
        // 2.1 遍历出所有的大类, parent_cid = 0
        List<CategoryEntity> list = categoryEntities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    //根据大类找到所有的小磊 递归的方式实现
                    categoryEntity.setChildrens(getCategoryChildrens(categoryEntity,categoryEntities));
                    return categoryEntity;
                }).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());
        // 2.2 根据大类找到对应的所有的小类

        return list;
    }

    /**
     * 逻辑批量删除操作
     * @param ids
     */
    @Override
    public void removeCategoryByIds(List<Long> ids) {
        //todo 判断类别数据是否在其它业务中使用
        //todo 批量逻辑删除操作
        baseMapper.deleteBatchIds(ids);
    }

    @Override
    public Long[] findCateLogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * @CacheEvict 在更新数据的时候同步删除缓存中的数据
     * @param category
     */
//    @CacheEvict(value = "catagory", key = "'getLevelCategory'") 表示删除catagory分区下的所有的缓存数据
//    @Caching(evict = {
//            @CacheEvict(value = "catagory", key = "'getLevelCategory'"),
//            @CacheEvict(value = "catagory",key = "'getCatalog2JSON'")
//    })
    @CacheEvict(value = "catagory",allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        //更新类别名称
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())){
            //同步更新级联数据
            categoryBrandRelationService.updateCatelogName(category.getCatId(),category.getName());
            //todo:更新其它的冗余数据
        }
    }
    public Map<String, List<Catalog2VO>> getCatalog2JSONRedis()
    {
        // 从redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(redis_key);
        if (StringUtils.isEmpty(catalogJSON)){
            System.out.println("缓存没有命中 ---->");
            //缓存中没有数据 需要从数据库中查询
            Map<String, List<Catalog2VO>> catalog2JSONForDb = getCatalog2JSONForDbWithRedisson();
            return catalog2JSONForDb;
        }
        System.out.println(" 缓存命中-------->");
        //表示缓存中拿到胃数据 , 直接从缓存中获取数据 然后返回
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return stringListMap;
    }

    /**
     * 查询所有分类数据, 并且完成一级 二级 三级 分类数据
     * @return
     */
    @Trace
    @Tags({
            @Tag(key = "getCatalog2JSON",value = "returnedObj")
            ,@Tag(key = "param",value = "arg[0]")
    })
    @Cacheable(value = "catagory",key = "#root.methodName",sync = true)
    @Override
    public Map<String, List<Catalog2VO>> getCatalog2JSON()
    {
        // 获取所有胡分类胡数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        //获取所有的一级分类的数据
        List<CategoryEntity> levelCategory = this.queryByParentCid(list, 0l);
        //把一级分类的数据转换为map容器, key就是一级分类编号, value就是一级分类的二级分类的数据
        Map<String, List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(
                key -> key.getCatId().toString(),
                value -> {
                    //根据一级分类的编号, 查询出对应的耳机分类的数据
                    List<CategoryEntity> l2Catalogs = this.queryByParentCid(list, value.getCatId());
                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2 -> {
                            //需要陈查询出来的二级分类的数据填充到对应的catalog2VOS中
                            Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                            //根据二级分类胡数据 找到对应的三级分类的信息
                            List<CategoryEntity> l3Catelogs = this.queryByParentCid(list, l2.getCatId());
                            if (l3Catelogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map(l3 -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                                // 三级分类关联 二级分类
                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }
        ));
        return map;
    }
    /**
     * 查询出所有的商品大类(一级分类)
     *  在注解中我们可以指定对应胡缓存的名称 起到一个分区的作用, 一般按照业务来区分.
     * @Cacheable({"catagory","product"}) 代表当前的方法返回结果是需要缓存的
     *      调用该方法的时候 如果缓存中有数据 该方法就不会执行
     *      如果缓存中没有数据 那么就执行该方法, 并且把查询出来的结果缓存起来
     *    缓存处理:
     *      1.存储在redis中的缓存数据的key是默认生成的, 缓存名称::SimpleKey[]
     *      2.默认缓存的数据胡过期时间是-1 永久
     *      3.缓存的数据 默认使用的是jdk胡序列化机制.
     *    改进:
     *      1.生成的缓存数据我们需要指定自定义的key key属性来指定, 剋直接字符串来定义通过spel表达式处理: #root.method.name
     *      2.指定缓存数据的存活时间 spring.cache.redis.time-to-live 指定过期时间
     *      3.把缓存的数据保存为json数据
     *  SpringCache的原理
     *      CacheAutoConfiguration ---> 根据指定胡spring.cache.type =
     * @return
     */
    @Cacheable(value = {"catagory"},key = "#root.method.name",sync = true)
    @Override
    public List<CategoryEntity> getLevelCategory() {
        System.out.println("查询了数据库 ..... ");
        List<CategoryEntity> list = baseMapper.queryLevelCategory();
        return list;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedisson(){

        // 获取分布式锁对象 加锁的时候, 这个锁的名称一定要注意
        // 商品信息 product-lock product-1001-lock product-1002-lock
        RLock lock = redissonClient.getLock("Catelog2JSON-lock");
        Map<String, List<Catalog2VO>> data = null;
        try {
            lock.lock();
            //加锁成功
            data = getDataForDB();
        } finally {
            // 释放锁
            lock.unlock();
        }
        //返回数据
        return data;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedis(){
        //加锁 , 在执行插入操作胡同时设置了过期时间
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (lock){
            System.out.println("获取分布式锁成功 .....");
            //加锁成功 , 给读一个你的key设置一定的过期时间
//            stringRedisTemplate.expire("lock",20,TimeUnit.SECONDS);
            Map<String, List<Catalog2VO>> data = null;
            try {
                data = getDataForDB();
            }finally {
                String scripts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end";
                //通过Redis的lua脚本实现 查询和删除操作的原子性
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(scripts,Long.class),Arrays.asList("lack"),uuid);
//            //获取当前key当前对应的值
//            String val = stringRedisTemplate.opsForValue().get("lock");
//            if (uuid.equals(val)){
//                //说明这把锁椒自己的
//                //从数据库中获取数据成功之后 我们应该庶释放锁
//                stringRedisTemplate.delete("lock");
//            }
            }
            //返回数据
            return data;
        }else {
            //加锁失败 休眠 重试
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("获取锁失败 ......");
            return getCatalog2JSONForDbWithRedis();
        }
        //释放锁
    }

    private Map<String, List<Catalog2VO>> getDataForDB() {
        // 先去缓存中查询有没有数据 , 如果有 返回,  否则查询数据
        // 从redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(redis_key);
        if (!StringUtils.isEmpty(catalogJSON)) {
            System.out.println("================》 catalogJSON = " + catalogJSON);
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
            });
            return stringListMap;
        }
        System.out.println("-----------》查询数据库操作");
        // 获取所有胡分类胡数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        //获取所有的一级分类的数据
        List<CategoryEntity> levelCategory = this.queryByParentCid(list, 0l);
        //把一级分类的数据转换为map容器, key就是一级分类编号, value就是一级分类的二级分类的数据
        Map<String, List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(
                key -> key.getCatId().toString(),
                value -> {
                    //根据一级分类的编号, 查询出对应的耳机分类的数据
                    List<CategoryEntity> l2Catalogs = this.queryByParentCid(list, value.getCatId());
                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2 -> {
                            //需要陈查询出来的二级分类的数据填充到对应的catalog2VOS中
                            Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                            //根据二级分类胡数据 找到对应的三级分类的信息
                            List<CategoryEntity> l3Catelogs = this.queryByParentCid(list, l2.getCatId());
                            if (l3Catelogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map(l3 -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                                // 三级分类关联 二级分类
                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }
        ));
        if (map == null){
            //那就说明数据库中的数据也不存在, 防止缓存穿透
            stringRedisTemplate.opsForValue().set(redis_key,"1",5, TimeUnit.SECONDS);
        }else{
            //从数据库中查询到的数据 我们需要给缓存中存储一份 , 防止缓存雪崩
            String json = JSON.toJSONString(map);
            stringRedisTemplate.opsForValue().set(redis_key,json,new Random().nextInt(10),TimeUnit.SECONDS);
        }
        return map;
    }

    /**
     * 查询出所有的二级和三级的分类的数据(从数据库中查询结果)
     *
     * 在springboot中默认情况下 椒单例
     * 在springboot中 默认情况下椒单例
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONForDb() {

        synchronized (this) {
            // 先去缓存中查询有没有数据 , 如果有 返回,  否则查询数据
            // 从redis中获取分类的信息
            return getDataForDB();
        }

    }

    /**
     * 跟进夫编号获取对应的子菜单信息
     * @param list
     * @param parentCid
     * @return
     */
    private List<CategoryEntity> queryByParentCid(List<CategoryEntity> list, long parentCid) {
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid().equals(parentCid);
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 查询出所有的二级和三级的分类的数据
     * @return
     */
//    @Override
//    public Map<String, List<Catalog2VO>> getCatalog2JSON() {
//        // 获取所有的一级分类的数据
//        List<CategoryEntity> levelCategory = this.getLevelCategory();
//        Map<String, List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(
//                key -> key.getCatId().toString()
//                , value -> {
//                    //根据一级分类的编号, 查询出对应的二级分类的数据
//                    List<CategoryEntity> l2Catalogs = baseMapper
//                            .selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", value.getCatId()));
//                    List<Catalog2VO> catalog2VOS = null;
//                    if (l2Catalogs != null) {
//                        catalog2VOS = l2Catalogs.stream().map(l2 -> {
//                            //需要查询出来的二级分类的数据 填充到对应的catalog2VO中
//                            Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
//                            List<CategoryEntity> l3Catelogs = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", catalog2VO.getId()));
//                            if (l2Catalogs != null) {
//                                //获取到的二级分类对应的三级分类的数据
//                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map(l3 -> {
//                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
//                                    return catalog3VO;
//                                }).collect(Collectors.toList());
//                                //三级分类 关联二级分类
//                                catalog2VO.setCatalog3List(catalog3VOS);
//                            }
//                            return catalog2VO;
//                        }).collect(Collectors.toList());
//                    }
//                    return catalog2VOS;
//                }
//        ));
//        return map;
//    }

    /**
     * 225,22,2
     * @param catelogId
     * @param paths
     * @return
     */
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if(entity.getParentCid() != 0){
            findParentPath(entity.getParentCid(),paths);
        }
        return paths;
    }

    /**
     *  查找该大类下的所有的小磊
     * @param categoryEntity 某个大类
     * @param categoryEntities 所有的类别数据
     * @return
     */
    private List<CategoryEntity> getCategoryChildrens(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(entity -> {
            //根据大类找到他们的直属的子类
            // 注意 Long数据 比较 不在 -128 127 之间的数据 是 new Long 对象
            return entity.getParentCid().equals(categoryEntity.getCatId());
        }).map(entity -> {
            //根据小类递归找到对应的小小类
            entity.setChildrens(getCategoryChildrens(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
        }).collect(Collectors.toList());
        return collect;
    }

}